﻿namespace NTLib.Pipe.Network.Connectors
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using NTLib.Core.Args;
    using NTLib.Pipe.Network.Descriptors;
    using NTLib.Core.Memory;
    using NTLib.Pipe.Network.Exceptions;
    using Streaming.Descriptors.Packages;
    using Streaming.Containers;

    /// <summary>
    /// Implement a connection throught network using tcp/ip
    /// </summary>
    public abstract class BaseConnector : Disposable, IConnector
    {
        #region Fields

        private Guid _id = Guid.NewGuid();
        private INetworkAddress _address;
        private ConnectionStatus _connected;

        private Task _connectTask;
        private bool _isInitialized;
        private bool _isHost;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the address.
        /// </summary>
        public INetworkAddress Address
        {
            get { return this._address; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance can pop item.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance can pop item; otherwise, <c>false</c>.
        /// </value>
        public abstract bool CanPopItem { get; }

        /// <summary>
        /// Gets the value indicating that the current pipe allow the user to push items through it.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance can push item; otherwise, <c>false</c>.
        /// </value>
        public abstract bool CanPushItem { get; }

        /// <summary>
        /// Gets a value indicating whether this <see cref="IConnector" /> is connected.
        /// </summary>
        /// <value>
        ///   <c>true</c> if connected; otherwise, <c>false</c>.
        /// </value>
        public virtual ConnectionStatus Connected
        {
            get { return this._connected; }
            protected set
            {
                if (this._connected != value)
                {
                    var oldValue = this._connected;
                    this._connected = value;
                    if (this.ConnectionChanged != null)
                        this.ConnectionChanged.Invoke(this, new ValueChangedEventArgs<ConnectionStatus>(oldValue, value));
                }
            }
        }

        /// <summary>
        /// Gets the identifier.
        /// </summary>
        public Guid Id
        {
            get { return this._id; }
            protected set { this._id = value; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is initialized.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is initialized; otherwise, <c>false</c>.
        /// </value>
        public bool IsInitialized
        {
            get { return this._isInitialized; }
        }

        /// <summary>
        /// Gets the value indicating if the connector is the host of the connection.
        /// </summary>
        protected bool IsHost
        {
            get { return this._isHost; }
        }

        #endregion

        #region Event

        /// <summary>
        /// Raised when The connection changed.
        /// </summary>
        public event EventHandler<ValueChangedEventArgs<ConnectionStatus>> ConnectionChanged;

        #endregion

        #region Methods

        /// <summary>
        /// Closes the connector.
        /// </summary>
        public async Task CloseAsync(CancellationToken token = default(CancellationToken))
        {
            CheckInitialized();
            await OnCloseAsync(token);
        }

        /// <summary>
        /// Connects the connector.
        /// </summary>
        public Task ConnectAsync(INetworkAddress address, CancellationToken token = default(CancellationToken))
        {
            CheckInitialized();

            if (this._connectTask == null)
            {
                this._address = address;
                if (this._address == null ||
                    this._address.Uri == default(Uri))
                    throw new BadNetworkAddressException("Address missing");
                if (!this.checkAddress(this._address))
                    throw new BadNetworkAddressException("Invalid format.");
                this._connectTask = OnConnectAsync(token);
                this._connectTask.ContinueWith(t => this._connectTask = null);
            }
            return this._connectTask;
        }

        /// <summary>
        /// Initializes the connector.
        /// </summary>
        public async Task InitializeAsync(bool isHost = false)
        {
            if (!this._isInitialized)
            {
                this._isInitialized = true;
                this._isHost = isHost;
                await this.OnInitializedAsync();
            }
        }

        /// <summary>
        /// Asynchroniously read data
        /// </summary>
        public Task<IPackage> ReadAsync(IPackageDescriptor desc, CancellationToken token = default(CancellationToken))
        {
            CheckInitialized();
            return OnReadAsync(desc, token);
        }

        /// <summary>
        /// Asynchroniously send data
        /// </summary>
        public Task SendAsync(IPackage pkg, CancellationToken token = default(CancellationToken))
        {
            CheckInitialized();
            return OnSendAsync(pkg, token);
        }

        /// <summary>
        /// Checks the validity of the address.
        /// </summary>
        /// <param name="connectionAddr">The connection addr.</param>
        /// <returns><c>True</c> if the address is valid</returns>
        protected abstract bool checkAddress(INetworkAddress connectionAddr);

        /// <summary>
        /// Checks the initialized.
        /// </summary>
        /// <exception cref="NTLib.Pipe.Network.Exceptions.ConnectorNotInitializedException"></exception>
        protected void CheckInitialized()
        {
            if (!this._isInitialized)
                throw new ConnectorNotInitializedException();
        }

        /// <summary>
        /// Initializes the connector.
        /// </summary>
        protected virtual async Task OnInitializedAsync()
        {
            await Task.Yield();
        }

        /// <summary>
        /// Dispose methods called whenever is the dipose origin.
        /// </summary>
        protected override void DisposeEveryTime()
        {
            base.DisposeEveryTime();
            if (this.Connected == ConnectionStatus.Connected)
            {
                try
                {
                    this.CloseAsync().Wait();
                }
                catch
                {
                    //TODO: treat the error possible correctly
                }
            }
        }

        /// <summary>
        /// Closes the connector.
        /// </summary>
        protected abstract Task OnCloseAsync(CancellationToken token = default(CancellationToken));

        /// <summary>
        /// Connects the connector.
        /// </summary>
        protected abstract Task OnConnectAsync(CancellationToken token = default(CancellationToken));

        /// <summary>
        /// Asynchroniously read data
        /// </summary>
        protected abstract Task<IPackage> OnReadAsync(IPackageDescriptor desc, CancellationToken token = default(CancellationToken));

        /// <summary>
        /// Asynchroniously send data
        /// </summary>
        public abstract Task OnSendAsync(IPackage pkg, CancellationToken token);

        #endregion
    }
}
